0X04 应用指纹识别

1.概念

应用指纹,其实是Web 应用的一种身份标识,具有唯一性。在Web 应用的开发过程中,为了提高开发的效率和系统的稳定性,通常会用到一些成熟、稳定的第三方环境、程序、框架或服务等,而这些第三方内容的名称或标识就是这里所说的应用指纹。

2.应用指纹种类及识别

对于一个简单的Web 应用而言,它所涉及的应用指纹信息非常多,这里为了便于理解和记忆,我们根据网络数据的流向,并结合分层思想,将常见的应用指纹分成了5 类, 如下:

(1)网络层指纹
网关、防火墙、VPN、CDN、DNS、路由器等基础设施指纹。

(2)主机层指纹
操作系统信息、软件防火墙、主机上各种对外提供服务的软件指纹。

(3)服务层指纹
Web 服务、FTP 服务、SSH 服务等各种对外提供服务的指纹。

(4)应用层指纹
各种建站程序、开源框架、前端框架等。

(5)语言层指纹
各种脚本语言信息, 如: ASP、ASPX、PHP 和JSP 等

应用指纹识别是通过对目标进行分析和判断,知道它是由哪些应用指纹组成的。举个例子,如果对安全宝的官网www.anquanbao.com进行应用指纹识别, 那么就能获取如下基础信息:

此处输入图片的描述

3.应用指纹识别技术

(1)获取特征的方式

既然应用指纹的价值如此之大,那么如何去识别和获取呢?首先需要获取指纹的特征,通常这些指纹特征会存在于特定页面的HTTP响应中,因此可以通过下面三种方式来处理:

(1)内容特征
这类指纹的特征在HTTP 响应的正文中, 通过对响应正文中的内容进行特征匹配即可识别

(2)页面特征
这类指纹在HTTP请求中并没有明显的特征,而页面的内容却相对固定,因此可以根据页面内容的Hash值进行识别。

(3)Headers 特征
这类指纹会在HTTP响应的消息报头中增加自己的报头信息,因此可以直接在消息报头中进行识别

(2)指纹特征库

按照文件的形式存储

此处输入图片的描述

(3)功能实现

应用指纹识别主要有三种常见的方式, 因此, 在代码实现中, 需要对这三部分内容进行关键特征匹配识别, 部分核心代码如下:

class FingerScan:
    '''
    '''
    def __init__(self):
        '''
        '''
        self._app_file = Settings.FINGER_FILE
        #指纹扫描模式:0为根域名扫描;1为自定义路径扫描模式
        self._scan_mode=0

        self._app_db = open(self._app_file,"rb").readlines()

        self._server_finger = None

        self._http_code    = None

    def md5(self,content):
        '''
        '''
        if isinstance(content,unicode):
            content = content.encode("utf-8")
        else:
            content = content

        m = hashlib.md5()

        try:
            m.update(content)
            return m.hexdigest()
        except:
            return None

    def set_mode(self,mode):
        '''
        '''
        self._scan_mode = mode        
    #{"wordpress":{"url":"/wp-admin.php","header":("server":"WAF/2.0"),"md5":"aaaaaaaaaaa"}}
    def scan_finger(self,site):
        '''
        '''
        app_name_list = []
        for item in self._app_db:
            # 注释掉则忽略
            if item.startswith("#"):
                continue
            dict_item =json.loads(item.strip())
            # 将 app 的键链接起来
            app_name = "".join(dict_item.keys()).strip()
            # 根据上面得到的键,获取值
            app_info = dict_item.get(app_name)
            # 因为值也是一个字典,因此要获取这个字典的 url 键对应的值
            url = app_info.get("url")

            # 获取 URL 类对象
            urlobj = URL(site)
            # 自定义路径扫描
            if self._scan_mode==1:
                test_url = urlobj.get_uri_string()
                if test_url.endswith("/"):
                    target_url = test_url[0:-1] + url
                else:
                    target_url = test_url + url
            # 根路径扫描
            else:
                test_url   = urlobj.get_netloc()
                target_url = urlobj.get_scheme()+"://"+test_url+ url

            log.info(target_url)
            try:
                # 发起请求
                res = wcurl.get(target_url)
            except:    
                continue

            # 得到结果
            dst_headers  = res.headers
            dst_body    = res.body

            self._http_code = res.get_code()

            try:
                self._server_finger = dst_headers["server"]
            except:
                pass

            if dst_body is None:
                continue

            # 计算 md5
            md5_body = self.md5(dst_body)

            key_list = app_info.keys()

            if "headers" in key_list:
                app_headers     = app_info.get("headers")
                app_key     = app_headers[0].lower()
                app_value     = app_headers[1]

                if app_key in dst_headers.keys():
                    dst_info = dst_headers.get(app_key)
                    result     = re.search(app_value,dst_info,re.I)
                    if result:
                        if "body" in key_list:
                            app_body = app_info.get("body")
                                            # 进行比较
                                            result = re.search(app_body,dst_body,re.I)
                                            if result:
                                                    app_name_list.append((target_url,app_name))
                        else:
                            app_name_list.append((target_url,app_name))

            elif "body" in key_list:
                app_body = app_info.get("body")
                # 进行比较
                result = re.search(app_body,dst_body,re.I)
                if result:
                    app_name_list.append((target_url,app_name))

            elif "md5" in key_list:
                app_md5 = app_info.get("md5")
                # 进行比较
                if app_md5 == md5_body:
                    app_name_list.append((target_url,app_name))

        return app_name_list

    def get_server(self):
        '''
        '''
        return self._server_finger


    def get_code(self):
        '''
        '''
        return self._http_code

if __name__=="__main__":
    '''
    '''
    if len(sys.argv)<2:
        print "Plz Input Site"
        sys.exit()

    fs = FingerScan()
    print sys.argv[1]
    test=fs.scan_finger(sys.argv[1])
    print test
    for item in test:
        print item
    print fs.get_server()

0X05 安全漏洞审计

1.漏洞审计三部曲

对于任何一种漏洞的检测或审计,都会遵循下面的流程:

(1) 需要分析现实中这个漏洞的各种场景。
(2) 构造出可以覆盖所有漏洞场景的扫描载荷(payload) 。
(3) 将其转化成扫描器的检测脚本并生成最终的扫描签名。

2.安全漏洞可以分成两类:

(1)通用型漏洞:

具有普遍们大部分应用都会涉及如 SQL 注入漏洞、 XSS 跨站湍洞、命令执行注入或文件包含漏洞等。它们主要是针对HTTP请求中的输入部分进行测试的,通过改变这些输入值就 可以对漏洞进行测试和判定

(2)Nday/0day 漏洞:

具有针对型,通常是指某一类具体应用, 比如:建站应用Discuz 的SQL 注入漏洞、IIS 的远程溢出漏洞或OpenSSL 的心脏出血漏洞等。

3.通用型漏洞审计

(1)SOL注入漏洞

原理我这里就省略了…

1.页面比较法

这种方法比较直观,也易于理解,而且准确度较高。 我们可以利用SQL语句来构造 恒真和恒假两种不同状态,如果目标存在 SQL 注入漏洞,那么恒真状态对页面内容的影响并不会产生较大的改变;而恒假状态则会明显地改变页面的内容,通过页面相似度算法比较这两个页面的相似程度,就可以判定目标是否存在SQL注入漏洞。

注: 说到这个页面相似度算法,我就想起了 sqlmap 的一个非常重要的东西: ratio ,这个是 sqlmap 中的重要组件之一,也是一种计算页面相似度的算法,在整个 sqlmap 中占有非常核心的地位

2.时间比较法

时间比较法主要是利用时间延迟技术进行漏洞的判定,虽然Web服务器可以隐藏错误或数 据,但是必定会返回HTTP响应信息, 因此可以向数据库中注入 时间延迟函数。 如果目标存在SQL注入漏洞,那么时间延迟函数就会被执行,服务端的响应时间就会延长,通过与正常服务端的响应时间比较, 可以判定目标是否存在漏洞。

3.扫描载荷

现在我们可以对 SQL 注入漏洞的场景进行整理,并给出最终的扫描载荷,扫描器利用它们可以对目标进行SQL注入检测, 如下:

此处输入图片的描述
此处输入图片的描述

4.代码实现

下面我贴出作者写的部分关键代码(GET 方法),并在必要的地方给出了注释方便理解

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

(2)XSS 漏洞

1.概念与区别

这里简单说一下 反射和 DOM XSS 的区别吧

反射型 XSS

反射性XSS, 其最明显的特征就是恶意数据通常会在链接里,需要受害者的参与,攻击者会将篡改后的链接发送给用户,用户访问这个链接后,恶意脚本会被浏览器执行。

DOM XSS

DOM型XSS, 是基于文档对象模型的一种XSS漏洞,客户端的脚本程序可以通过DOM动态地操作和修改页面内容。它不依赖于提交数据到服务端(这或许是两者的最大差别了,其实就是会不会与服务器进行交互),但如果从客户端获取DOM中的数据没有进行过滤,那么攻击者就可以注入恶意代码,并在浏览器端执行,产生DOM型XSS。

2.利用场景

反射型就不说了,然后存储型提一点就是输入点和输出点一般不在一个页面,这里说一下 DOM 型吧。

利用document对象的相关属性来获取前端的输入内容,然后传到eval函数中执行。

页面代码:

此处输入图片的描述

前端输入

此处输入图片的描述

执行结果

此处输入图片的描述

DOM型XSS漏洞常见的输入输出点如下表:

此处输入图片的描述

注意:

现在主流浏览器都已经增加了对XSS攻击的防护,也就是我们常说的XSSFilter(XSS过滤器),如Chrome通过内置过滤器XSS-Auditor进行过滤,Firefox通过NoScript扩展支持该功能等。所以对于一些常用的payload,浏览器都会有相应的干扰策略,它们会阻碍正常的检测与识别。因此在真实的扫描中,通常需要绕过后才能进行有效的检测。

2.检测原理
(1)反射型XSS

从上面的漏洞场景来看, 反射型XSS漏洞具有明显的输入/输出特点,而且数据提交的页面和数据输出的页面是同一个, 因此可以通过构造扫描载荷(payload)进行提交,然后检查输出的内容就可以判定目标是否存在反射型XSS漏洞。

(2)存储型XSS

存储型 XSS 与反射型 XSS 的场景基本相同,只不过存储型会向服务端插入数据如果用户数据提交的页面与输入内容的展示页面相同,那么可以通过对输出内容进行检测来判定;如果用户数据提交的页面与输入内容的展示页面不同,这种情况就需要先找到输入内容的展示页面, 并在该页面中进行检测和判定

(3)DOM型XSS

与前面两种XSS 漏洞类型不同,DOM 型XSS 是在浏览器的解析中,改变当前页面的DOM树,对于这种交互操作较多的单页面,可以借助浏览器引擎进行检测,但如果每一个页面都增加这些交互操作,那么就会严重影响扫描器效率,所以这里暂不实现该类型(说你个锤子….等我找别的扫描器分析吧…坑)。

下面对漏洞场景中的漏洞检测方法进行整理和覆盖,并给出最终的扫描载荷列表。在实际的测试过程中,我们发现<script>标签经常会被一些防护设备作为特征过滤,从而产生干扰,因此在实践中我们用<a>标签作为特征进行检测。

此处输入图片的描述

最终的扫描载荷可以定义为下列形式:

此处输入图片的描述

具体的内容如下:

此处输入图片的描述

因此, 通过这个扫描载荷就可以覆盖上面描述的所有场景。

下面是具体的代码实现。由千其他类型的XSS 并不容易在扫描器中进行通用的检测,所以这里主要选择反射型XSS 来实现。根据上述的检测原理, 并结合最终的扫描载荷,

具体的代码实现如下:

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

我这里需要稍微解释一下:

(1)首先我们需应该自定义很多很多带标签的用来 fuzz 的字符串(这里作者由于是 demo
于是只定义了一个,实际中是远远不够的),而且标签可以是<img> <svg> 之类的,不一定是 <a>

(2)那么为什么这个标签最后能在页面中完整返回就说明漏洞可能存在呢?因为完整返回说明了我们能够在页面源码中注入我们的标签,这样我们就有利用的可能

(3)命令执行漏洞

1.概念与原理

通常情况下,如果Web应用程序需要执行系统命令,那么开发人员会将客户端获取的数据直接传递给具有执行系统命令功能的函数中, 如: system、exec、shell_exec等。如果没有对客户端的数据进行过滤就产生命令执行注入漏洞,那么攻击者可以通过命令注入来执行额外的命令,从而达到攻击的效果。

2.利用场景
场景一

将前端获取的变量直接与命令语句进行拼接,然后代入命令执行函数中,这里以Linux操作系统为例说明。

后端代码:

此处输入图片的描述

从上面的代码中可以看到, 由千变量位千语句的最后面, 因此可以利用 Linux 中的 些特殊符号完成命令的注入。在构造扫描载荷 (payload) 之前, 先来看看这些特殊符号的作用, 如下:

1.管道符号(|)
可以连接多个命令,它会把第一个命令command1 执行的结果作为第二个命令command2 的输入传给command2 并执行。

2.连接符号(;)
可以连接多个命令, 它会依次顺序地执行这些命令。

3.逻辑与符号(&&)
可以连接多个命令, 只有第一个命令执行成功, 才会执行第二个命令。

4.逻辑或符号 (||)
可以连接多个命令, 只有第一个命令执行失败, 才会执行第二个命令;否则不会执行第二个命令。

上面所介绍的符号都可以用来连接多个命令,根据每个符号的特点, 可构造对应的扫描载荷(payload)。

前端输入:

此处输入图片的描述

访问链接 http://localhost/book/cmd/1_cmd.php?data=test;id;

此处输入图片的描述

从上面的效果截图中可以看到,程序除了成功执行ls命令外,还执行了额外的id命令,从而成功完成命令执行注入攻击。

场景二

将前端获取的变量通过单引号或双引号,代入命令执行函数中

后端代码:

此处输入图片的描述

由于可控变量在单引号之间,所以需要先对单引号进行闭合,然后再利用分号的特性进行后续的命令注入, 因此可以构造如下的payload进行检测

此处输入图片的描述

访问链接 http://localhost/book/cmd/2_cmd.php?data=test';id;'

此处输入图片的描述

场景三

将用户的输入赋值给某个变量

后端代码:

此处输入图片的描述

在对场景3进行分析之前, 我们需要先来了解一下PHP的一些特性。在PHP中, 字符串的定义可以使用单引号, 也可以使用双引号。

它们的区别是:
双引号串中的变扯将被解析而且替换,而单引号串中的内容总被认为是普通字符,不具备任何解析功能。

下面分别对可变变量可变函数print函数进行讲解

1.可变变量:

PHP中提供了一种其他类型的变量, 称之为可变变量。就是说, 一个变量的变量名可以动态设置和使用。例如一个普通的变量通过声明来设置, 如下:

此处输入图片的描述

一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。在上面的例子中,hello 使用了两个美元符号($)以后, 就可以作为一个可变变量的变最了, 如下:

此处输入图片的描述

这时, 我们会发现这里定义了两个变量:变量$a 的内容是”hello” , 并且变量$hello 的内容是”world”。也可以用下面的语句来定义, 它们的效果是一致的, 如:

此处输入图片的描述

这里使用"${$a}" 来替换"$$a" 主要利用大括号在变量间接引用中进行定界, 避免歧义。

2.可变函数

PHP同时也支持可变函数。这意味着如果一个变量名后有圆括号,PHP将寻找与变量的值同名的函数,并且尝试执行它。例如:

此处输入图片的描述

代码中的"$a()" 属于可变函数, 结合可变变量的用法, 这里以函数来替换变量, 即为:${print(md5(imiyoo))}。这样就可以执行print()函数了。由千print函数比较特殊, 这里有必要说明一下。

3.print函数

print 函数实际上不是一个真正意义上的函数,而是一个语言结构,因此它可以不必使用括
号。但由千它具备函数的形式, 所以也可以使用带括号的形式。写成如下的形式也是可以的:

此处输入图片的描述

那么现在针对场景三, 我们可以构造如下的语句进行测试。

前端输入:

此处输入图片的描述

访问链接 http://localhost/book/cmd/3_cmd.php?data=test;${print(md5(imiyoo)) }。

测试效果如下:

此处输入图片的描述

场景四:

用户的输入以单引号或双引号的形式赋值给某个变量。

此处输入图片的描述
此处输入图片的描述

由于变量在单引号里面,因此需要先将两边的单引号闭合,然后用分号来分割语句,这样就能执行PHP代码了,构造输入如下。

前端输入:

此处输入图片的描述

访问链接 http://localhost/book/cmd/4_cmd.php?data= test’;${print(md5(imiyoo)) };’.

测试效果如下:

此处输入图片的描述

场景五:

将用户的输入作为数组的key进行赋值。

此处输入图片的描述

这里前端输入的变量作为数组的key, 为了执行代码, 需要从key的位置中跳出来,因此可以通过下面的形式进行闭合, 构造如下语句。

前端输入:

此处输入图片的描述

访问链接 http://localhost/book/cmd/5_cmd.php?data=test"]= 1;$ {print(md5(imiyoo))};//

测试效果如下:

此处输入图片的描述

3.检测原理

下面讲一下命令执行注入的检测原理。

首先,需要结合漏洞的场景,对原有的语旬逻辑进行闭合
然后,通过特性字符或特性用法注入有预期输出的命令语句
最后,根据响应输出的内容进行漏洞判定

如果目标存在命令注入执行漏洞,那么预期的内容就会显性地输出到页面。 同理,如果目标不存在该漏洞,那么页面就不会出现预期的内容。

下面构造该漏洞对应的扫描载荷,将命令执行注入漏洞的场景及检测数据整理如下表:

此处输入图片的描述

该漏洞场景实质上覆盖了两类情况: 一类是系统层面的命令执行;另一类是应用层面的命令执行

由于它们的命令特征函数并不相同,因此可以分类对其进行检测。根据上面的检测原理和对应的扫描载荷, 来实现针对命令执行注入漏洞的检测,

部分代码如下:

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

(4)文件包含漏洞

1.漏洞的分类

分为远程文件包含合本地文件包含,本质上是一样的(当然在 PHP 的设置上有些不同),本书只介绍本地文件包含漏洞

2.漏洞利用场景
场景一:

将前端获取的变量直接传递给文件包含函数。

后端代码:

此处输入图片的描述

这里将前端获取的文件名直接传递到文件包含函数中,中间没有任何过滤,因此我们可以控制包含的文件名变最。当前端输入如下的内容:

此处输入图片的描述

那么就可以直接读取 Linux 系统中的账号和密码文件了(当然这个文件的是普通用户可以读的,不能说明权限问题,另外如果可能还会受到 open_basedir 的路径限制,下面我们会说到), 如下图:

此处输入图片的描述

但在实际的测试中,像lnmp这种一键集成环境部署工具本身就已做过一些安全加固措施,在网站的根目录下会有一个 .user.ini 隐藏文件,会限定文件的读取目录,内容如下:

此处输入图片的描述

因此,我们只能在限定路径内去寻找有预期输出的文件进行漏洞判定。针对当前的环境,有下面两种方式:

方法一:

利用/proc/ 目录下的文件, Linux 内核提供了一种通过/proc文件系统,在运行时访问内核内部数据结构、 改变内核设置的机制。它以文件系统的方式为访问系统内核数据的操作提供接口,用户和应用程序可以通过proc得到系统的信息。该目录下可作为预期输出的文件,如下表:

此处输入图片的描述

方法二:

有针对性地去找一些系统默认的配置文件和隐藏文件,比如,在当前的lnmp环境下,可以通过隐藏文件.user.ini进行漏洞检测和判定。为了描述简单和易于理解, 这里用第一种方法进行语句构造。

前端输入:

此处输入图片的描述

访问链接: http://localhost/book/lfi/1_lfi.php?data=/proc/meminfo

测试效果如下:

此处输入图片的描述

场景二

将前端获取的变量名通过拼接目录名,直接传递给文件包含函数。

后端代码:

此处输入图片的描述

代码中的文件名前面还有目录名,程序员本来是想只允许包含该目录下的文件,但由于这里并没有任何限制, 因此, 可以利用目录跳转”../“进行突破, 跳出当前所在的目录, 这样就又可以包含任意文件了。

由于这里并不知道上级目录的级数,没有合适的参考路径,所以需要用多级目录跳转,直至根目录,然后从根目录开始选择有预期输出的文件, 因此构造如下的语句。

前端输入:

此处输入图片的描述

访问链接 http://localhost/book/lfi/2_lfi.php?data=../../../../../../../../../../proc/meminfo

测试效果如下图:

此处输入图片的描述

场景三

将前端获取的变量通过拼接扩展名,然后直接传递给文件包含函数。

此处输入图片的描述

这里程序限制了文件的扩展名,由于只能包含特定扩展名的文件,这样就不能包含有预期输出的文件了,所以也就无法进行正常的检测。但是可以利用字符串截断的特性进行突破

%00截断

十六进制0X00是字符串结束的标志。如果是字符串类型,在遇到0X00时就会截断,其后的字节不会再作为字符串的内容。这样就可以利用0X00来截断后面的扩展名,从而可以包含任意文件。%00是0X00在URL中的表现形式,因此可以构造如下的语句进行检测:

此处输入图片的描述

长度截断

通常Windows的截断长度为240, Linux的截断长度为4096。由于Windows和Linux的文件名都有一个最大路径长度(MAX]ATH)的限制,因此当提交文件名的长度超过了最大路径长度的限制时就会截断后面的内容,从而可以无障碍地包含任意文件。

在实际的测试中, 可以用一定数最的字符”.”、”/“或者”./“来突破操作系统对文件名的最大长度限制,截断后面的字符串。

注意:
%00截断和长度截断在PHP5.4以上版本都已经修复,因此在测试环境中并不能重现,但这也是一种攻击的重要思路,依然需要重点掌握。

文件包含漏洞的检测其实相对简单, 因为它有非常明显的输入和输出,所以只需要根据不同的漏洞场景,通过构造语句读取有预期输出的文件,然后通过相应的特征匹配, 就可以实现漏洞检测。如果目标存在漏洞, 那么对应的文件就会被读取,而文件中的内容也会被输出到响应页面中,这样就可以根据文件的内容特征进行匹配, 从而进行后续的漏洞判定。

扫描载荷的情况如下表:

此处输入图片的描述

在文件包含的检测中,经常会碰到一种情况:在原始响应页面中就存在特征内容, 这样就会造成明显的误报, 因此需要先对原始请求的内容进行预判,然后再进行对应的漏洞检测。具
体的核心检测代码如下:

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

这里面的函数书写模式和之前的其他漏洞的检测模式基本一样,这里就不再多解释了

(5)敏感文件泄露

1.概念以及原理

敏感文件泄露主要由于人为的疏忽或工具的特性等原因所致。由于没有技术含量,所以经常不被人重视。但它却最容易导致服务器被攻击和入侵,攻击者利用这些细节可以有效地探测到敏感文件信息,而这些敏感文件通常包含账号、密码等重要信息,然后利用这些账号信息访问未授权系统实现进一步攻击,从而完成最终的入侵和数据窃取。

2.敏感文件泄露漏洞场景
场景一

管理员为了对网站数据进行备份,直接对网站目录下的所有文件打包,并将其存放在网站的web根目录下。同时为了简单易记通常会将其命名如:wwwroot.rar、wwwroot.zip、1.zip、w.zip、bak.zip等,而网站目录中的这些文件,在没有额外控制策略的情况下,任何人都可以直接访问和下载

场景二

开发人员在使用版本控制工具(如: GIT、SVN、CVS等)进行项目部署时,没有删除根目录下的隐藏备份文件。攻击者利用这些文件可换取项目源代码或配置文件等敏感信息, 从而完成后续的攻击和入侵。

SVN敏感信息泄露

SVN (Subversion)是一个自由、 开源的项目源代码版本控制工具。 目前,绝大多数开源软件和企业代码管理, 都使用SVN作为代码版本管理软件。 开发人员使用”svn checkout” 来检出项目代码时, 在项目根目录下会有一个隐藏目录.svn, 内容如下:

此处输入图片的描述

其中, 项目源码文件都备份在pristine目录下,we.db是一个SQLite数据库文件,里面记录着项目源码文件在pristine目录下的对应路径,可以通过下面命令获取:

此处输入图片的描述

而对于SVN的1.6.X及以下版本,则可以通过对.svn隐藏目录中的entries文件进行解析,这样就可以获取项目源码的目录结构和文件内容。entries文件的解析也非常简单, 如下:

此处输入图片的描述

注意:
SVN的1.6.X及以下版本是在每个文件夹都生成一个.svn隐藏文件夹,而SVN的1.7.X版只在版本库根目录下生成一个.svn隐藏文件夹,当给线上环境进行项目部署时,需要删除svn隐藏目录,或使用svn export进行项目部署; 也可以在服务器上进行配置 ,禁止访问.svn目录

Git敏感信息泄露

Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。很多企业也会选择使用它作为代码版本控制。当使用git clone进行线上项目部署时,git会把项目的信息隐藏在一个.git的文件夹里, 如下:

此处输入图片的描述

其中,index文件实际上是一个包含文件索引的目录树,它记录了文件名、文件内容的SHA1哈希值和文件访问权限。ojbects目录中存放着所有Git对象,也包含项目源码的备份文件。通过对index文件进行解析,就能找到源文件在objects目录的对应关系,从而获取对应的源文件内容。

可以用git ls-files — stage命令来解析index文件, 如下

此处输入图片的描述

备份文件对象在objects目录的存储原则为: SHA1 哈希值的前两位是文件夹名称, 后38
位作为对象文件名。比如,上图中README.md的文件SHA1哈希值为:adeb458def7e52e3ablb4ac586ac49ef49c5b373, 那么对应的文件路径则为:objects/ad/eb458def7e52e3abl4ac586ac49ef49c5b373, 两个文件的内容是 致的, 如下

查看备份文件的内容:

此处输入图片的描述

在Git系统中, 备份文件对象有两种存储方式, 一种是松散对象存储, 就是前面提到的;另一种是打包对象存储, 它会对松散对象中的文件进行打包存储,此时objects的目录结构如下:

此处输入图片的描述

如果要想获取项目源文件信息,那么需要先对其进行解包,然后按照松散对象的方式来获取,解包的具体操作方式如下:

此处输入图片的描述

至于打包文件的文件名,可以从 objects/info/packs 中获取,当使用 git gc 命令对松散对象进行打包时, 会在 objects/info/packs 文件中记录打包对象的文件名信息.

场景三

如果开发人员在线上环境临时修改代码, 那么一些编辑工具会自动产生对应的备份文件,一旦疏忽, Web目录中就会留下.bak 、.swp 、.old 或~等扩展名的备份文件,特别是一些数据库配置文件。攻击者可以根据这个特性探测和获取目标的敏感信息

比如, Linux 下常用的编辑器Vi, 其特性为:当使用Vi 打开一个文件时,在同目录下会生成一个swp扩展名的隐藏文件,它主要起到临时备份和还原的作用。如果文件正常退出, 那么这个文件会自动删除, 没有影响;但如果文件异常退出或处正在于编辑时,那么这个文件就会持续存在。此时攻击者就可以下载该文件来获取敏感信息, 如下图:

此处输入图片的描述

然后通过vi -r即可恢复

3.敏感文件泄露的检测原理
1.压缩、备份类文件检测

对于压缩类的文件检测,只需要构造文件对应的 URL, 然后向目标请求该 URL. 根据HTTP响应中的状态码及文件类型进行判断。 在这里其实并不需要获取 HTTP 响应中的响应体信息, 只要通过响应头的 Content-type 字段的值即可判断。 为了提高检测效率 可以使用 HTTP 请求中的HEAD方法进行快速处理;而对于备份类的文件检测, 只需要关注动态脚本文件,并根据脚本语言的源码特征进行检测即可。

2.版本类文件检测

版本类文件的检测, 可以根据特征文件的Content-type进行判断。 对于SVNl.6.X及以下的版本,可以通过.svn/entries文件进行检测:对千SVNl.7.X以上的版本,可以通过.svn/wc.db 文件进行检测;对千Git敏感文件泄露, 可以根据.git/index这个文件进行检测。这些文件都属千二进制流文件, 因此其Content-type类型都是application/octet-stream。

在敏感文件的检测中, 会对常见的压缩文件、 备份文件和版本文件进行探测和验证,查看目标是否存在敏感文件泄露,

代码实现:

压缩、 备份类文件检测的部分检测代码如下

此处输入图片的描述

此处输入图片的描述

此处输入图片的描述

版本类文件检测的部分检测代码如下:

此处输入图片的描述

0X06 参考链接

http://yuedu.163.com/book_reader/cc457ea1464d4bb6bd27a2082658a434_4/b517da182e6b4c119de3eca5f3891c91_4